/*
 * Copyright (c) 2022 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */


import java.util.ArrayList;
import java.util.Arrays;
import java.util.Random;

class Obj {
    public double value;
    Obj(double value) {
        this.value = value;
    }
}
class MethodCall {
    public static ArrayList<Integer> GenerateRandoms() {
        ArrayList<Integer> result  = new ArrayList<>();
        for (int i = 0; i < 2; i++) {
            int randomNum = (int) (Math.floor(Math.random() * 2) + 1);
            result.add(randomNum);
        }
        return result;
    }
    public static ArrayList<Integer> generaterandoms = GenerateRandoms();
    public  static int global_value = 0;
    public  static  Integer[] GenerateFakeRandomInteger() {
        Integer[] resource = new Integer[]{12, 43, 56, 76, 89, 54, 45, 32, 35, 47, 46, 44, 21, 37, 84};
        return resource;
    }

    public static Obj[] GenerateFakeRandomObject() {
        Obj[] resource = new Obj[15];
        Arrays.fill(resource, new Obj(0));

        for (int i = 0; i < 15; i++) {
            double random = Math.random() * (10) + 1;
            resource[i] = new Obj(random);
        }
        return resource;
    }

    public  static Integer[] arr = GenerateFakeRandomInteger();

    public static class ClassFunc {
        public int foo(Integer[] resources,int i, int i3, int resourcesLength) {
            if ((resources[i % i3 & (resourcesLength - 1)] & 1) == 0) {
                i3 += 1;
            } else {
                i3 += 2;
            }
            return i3;
        }
        public Obj parameterlessFoo() {
            Obj[] res = {new Obj(0), new Obj(0), new Obj(0), new Obj(0), new Obj(0)};
            Obj[] resources = GenerateFakeRandomObject();
            for (int i = 0; i < 200; i++) {
                res[i % 5] = resources[i % 15];
            }
            return res[1];
        }


        public int differentFoo(Integer[] resources, int i , int i3, int resourcesLength) {
            if ((resources[i % i3 & (resourcesLength - 1)] & 1) == 0) {
                i3 += 1.1;
            } else {
                i3 += 2.1;
            }
            return i3;
        }

        public int defaultFoo(Integer[] resources, int i, int i3, int resourcesLength) {
            if ((resources[i % i3 & (resourcesLength - 1)] & 1) == 0) {
                i3 += 1;
            } else {
                i3 += 2;
            }
            return i3;
        }

        public void variableFoo(int a, char b, boolean c) {
            arr[global_value] += 1;
        }
        public void argsFoo(int[]... args) {
            global_value++;
        }
    }

    /***************************** With parameters *****************************/
// Method call
    public static double RunMethodCall() {
        int count = 10000000;
        ClassFunc cf = new ClassFunc();
        Integer i3 = 1;
        Integer[] resources = GenerateFakeRandomInteger();
        Function<Integer[], Integer, Integer, Integer, Integer> func = cf::foo;
        int resourcesLength = resources.length;
        double startTime = System.nanoTime();
        for (int i = 0; i < count; i++) {
            i3 = func.apply(resources, i, i3, resourcesLength);
        }
        double midTime = System.nanoTime();
        for(int i=0;i<count;i++) {
        }
        double endTime = System.nanoTime();
        double time = ((midTime - startTime) - (endTime - midTime)) / 1000000;
        System.out.println("res:" + resources);
        System.out.println("Function Call - RunMethodCall:\t"+time+"\tms");
        // print("Method Call:\t"+String(time)+"\tms");

        return time;
    }

    public static double RunParameterlessMethodCall() {
        int count  = 10000;
        ClassFunc cf = new ClassFunc();
        global_value = 0;
        Obj i3 = new Obj(1);
        Supplier<Obj> func = cf::parameterlessFoo;
        double startTime = System.nanoTime();
        for (int i = 0; i < count; i++) {
            i3 = func.apply();
        }
        double midTime = System.nanoTime();
        for(int i=0; i<count; i++) {
        }
        double endTime = System.nanoTime();
        double time = ((midTime - startTime) - (endTime - midTime)) / 1000000;
        System.out.println("res:" + i3);
        System.out.println("Function Call - RunParameterlessMethodCall:\t"+time+"\tms");
        return time;
    }

    /***************************** Default parameters *****************************/

// Method call
    public static double RunDefMethodCall() {
        int count = 10000000;
        ClassFunc cf = new ClassFunc();
        global_value = 0;
        Integer i3 = 1;
        Integer[] resources = GenerateFakeRandomInteger();
        int resourcesLength = resources.length;
        double startTime = System.nanoTime();
        Function<Integer[], Integer, Integer, Integer, Integer> func = cf::defaultFoo;
        for (int i = 0; i < count; i++) {
           i3 = func.apply(resources,i ,i3,resourcesLength);
        }
        double midTime = System.nanoTime();
        for(int i=0;i<count;i++) {
        }
        double endTime = System.nanoTime();
        double time = ((midTime - startTime) - (endTime - midTime)) / 1000000;
        System.out.println("res:" + i3);
        System.out.println("Function Call - RunDefMethodCall:\t"+time+"\tms");
        return time;
    }

    /********************* Different parameters *********************/

// Method call
    public static double RunDifMethodCall() {
        int count = 10000000;
        ClassFunc cf = new ClassFunc();
        global_value = 0;
        double i3  = 1.1;
        Integer[] resources = GenerateFakeRandomInteger();
        int resourcesLength = resources.length;
        Function<Integer[], Integer, Integer, Integer, Integer> func = cf::differentFoo;
        double startTime = System.nanoTime();
        for (int i = 0; i < count; i++) {
           i3 = func.apply(resources, i, (int) i3, resourcesLength);
        }
        double midTime = System.nanoTime();
        for(int i=0;i<count;i++) {
        }
        double endTime = System.nanoTime();
        double time = ((midTime - startTime) - (endTime - midTime)) / 1000000;
        System.out.println("res:" + i3);
        System.out.println("Function Call - RunDifMethodCall:\t"+time+"\tms");
        return time;
    }

    /************************* Variable parameters *************************/

// Method call
    public static double RunVariableMethodCall() {
        int count  = 10000000;
        ClassFunc cf = new ClassFunc();
        global_value = 0;
        //int a, char b, boolean c
        TriConsumer<Integer, Character, Boolean> func = cf::variableFoo;
        double startTime = System.nanoTime();
        for (int i = 0; i < count; i++) {
            func.apply(1, '1', true);
        }
        double midTime = System.nanoTime();
        for(int i=0;i<count;i++) {
        }
        double endTime = System.nanoTime();
        double time = ((midTime - startTime) - (endTime - midTime)) / 1000000;
        
        System.out.println("Function Call - RunVariableMethodCall:\t"+time+"\tms");
        return time;
    }
}

class Benchmark {
    public static void main(String[] args) {
        MethodCall.RunMethodCall();
        MethodCall.RunParameterlessMethodCall();
        MethodCall.RunDefMethodCall();
        MethodCall.RunDifMethodCall();
        MethodCall.RunVariableMethodCall();
        // System.out.println("Ts Method Call Is End, global_value value: \t" + global_value);
    }
}

@FunctionalInterface
interface Function<T, U, V, W, R> {
    R apply(T t, U u, V v, W w);
}

@FunctionalInterface
interface TriConsumer<T, U, V> {
    void apply(T t, U u, V v);
}

@FunctionalInterface
interface Supplier<R> {
    R apply();
}



